package cn.amossun.starter.mybatis.data.security.rewrite;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.map.MapUtil;
import cn.amossun.starter.mybatis.data.security.enums.SqlDmlTypeEnum;
import cn.amossun.starter.mybatis.data.security.rewrite.condition.DecryptEncryptCondition;
import cn.amossun.starter.mybatis.data.security.rewrite.condition.DmlDecryptEncryptCondition;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import cn.amossun.starter.mybatis.data.security.core.CCJSQLStatementContext;
import cn.amossun.starter.mybatis.data.security.rule.MybatisEncryptRule;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.Map;

/**
* @description: 加密重写
* @author: Amos.Sun
* @DateTime: 2020/3/9 19:48
**/
@Slf4j
@Data
public class EncryptPreParameterRewriter {

    public EncryptPreParameterRewriter(MybatisEncryptRule mybatisEncryptRule) throws Exception {
        this.encryptRule = mybatisEncryptRule;
    }

    private final MybatisEncryptRule encryptRule;

    public void rewrite(String sql, List<ParameterMapping> parameterMappings, Object parameterObject) throws Exception {

        CCJSQLStatementContext context = new CCJSQLStatementContext(sql, encryptRule);

        DmlDecryptEncryptCondition dmlDecryptEncryptCondition = context.findEncryptConditions();
        if (dmlDecryptEncryptCondition == null) {
            return;
        }
        List<DecryptEncryptCondition> encryptConditions = dmlDecryptEncryptCondition.getEncryptConditions();
        if(encryptConditions.isEmpty()){
            return;
        }
        Map<String, Object> paramValue = MapUtil.newHashMap(parameterMappings.size());
        for (DecryptEncryptCondition encryptCondition : encryptConditions) {
            encryptParameters(encryptCondition, parameterMappings, parameterObject, paramValue, dmlDecryptEncryptCondition.getSqlDmlTypeEnum());
        }
    }

    protected void encryptParameters(DecryptEncryptCondition encryptCondition, List<ParameterMapping> parameterMappings, Object parameterObject, Map<String, Object> paramValue, SqlDmlTypeEnum sqlDmlTypeEnum) throws Exception {

        int index = encryptCondition.getIndex();

        if (CollectionUtil.isNotEmpty(parameterMappings) && parameterObject != null) {
            //获取参数
            ParameterMapping parameterMapping = parameterMappings.get(index - 1);
            String property = parameterMapping.getProperty();

            //获取参数值
            MetaObject metaObject = SystemMetaObject.forObject(parameterObject);
            Object originalValue = metaObject.getValue(property);

            //判断当前参数值是否已加密处理
            if(!paramValue.containsKey(property)) {
                Object encryptedValue = encryptRule.getEncryptedValue(encryptCondition.getTableName(), encryptCondition.getColumnName(), originalValue, sqlDmlTypeEnum);
                metaObject.setValue(property, encryptedValue);
                paramValue.put(property, encryptedValue);
            }
        }
    }

    public String getEncryptedValue(String encryptValue) throws Exception {
        return String.valueOf(encryptRule.getEncryptedValue(null, encryptValue));
    }

    public String base64EncryptValue(String encryptValue) throws BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, InvalidKeyException, NoSuchPaddingException, UnsupportedEncodingException {
        return encryptRule.base64EncryptValue(encryptValue);
    }

    public String getEncryptedValueOrNull(String encryptValue)  {
        try {
            return getEncryptedValue(encryptValue);
        } catch (Exception exception) {
            log.error("加密异常: {}.", exception.getMessage());
        }
        return null;
    }

    public String getEncryptedValueOrDefault(String encryptValue)  {
        try {
            return getEncryptedValue(encryptValue);
        } catch (Exception exception) {
            log.error("加密异常: {}.", exception.getMessage());
        }
        return encryptValue;
    }

}
